#region using

using System;
using System.Configuration;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

#endregion

namespace PS.Tools.Configuration
{
    /// <summary>
    /// Represents a strongly typed XML section within a configuration file
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class XmlSection<T> 
        : ConfigurationSection 
    where 
        T : class
    {
        private XmlSerializer serializer;
        private T configurationItem;

        #region Get section

        /// <summary>
        /// Retrieves the specified XML section for the current application's default
        /// configuration
        /// </summary>
        /// <param name="sectionName"></param>
        /// <returns></returns>
        public static T GetSection(string sectionName)
        {
            XmlSection<T> section = (XmlSection<T>)ConfigurationManager.GetSection(sectionName);
            if (null == section)
                return null;
            else
                return section.configurationItem;
        }

        /// <summary>
        /// Retrieves the specified XML section for the specified System.Configuration.Configuration object
        /// </summary>
        /// <param name="sectionName">The name of the section</param>
        /// <param name="configuration"></param>
        /// <returns></returns>
        public static T GetSection(string sectionName, System.Configuration.Configuration configuration)
        {
            XmlSection<T> section = (XmlSection<T>)configuration.GetSection(sectionName);
            if (null == section)
                return null;
            else
                return section.configurationItem;
        }

        #endregion

        #region Load configuration

        private static XmlSerializer GenerateSerializer(Type type, string rootElementName)
        {
            XmlRootAttribute root = new XmlRootAttribute(rootElementName);
            XmlSerializer serializer = new XmlSerializer(type, root);

            return serializer;
        }

        protected override void Init()
        {
            base.Init();

            // Load the serializer from the cache
            serializer = GenerateSerializer(typeof(T), SectionInformation.Name);
        }

        /// <summary>
        /// Deseializes the XML section
        /// </summary>
        /// <param name="reader"></param>
        protected override void DeserializeSection(XmlReader reader)
        {
            configurationItem = (T)serializer.Deserialize(reader);
        }

        #endregion

        #region Support for saving configuration

        /// <summary>
        /// Serializes the XML section
        /// </summary>
        /// <param name="parentElement"></param>
        /// <param name="name"></param>
        /// <param name="saveMode"></param>
        /// <returns></returns>
        protected override string SerializeSection(ConfigurationElement parentElement, string name, ConfigurationSaveMode saveMode)
        {
            XmlSectionWriter sectionWriter = new XmlSectionWriter(new StringWriter());
            serializer.Serialize(sectionWriter, configurationItem);
            return sectionWriter.ToString();
        }

        /// <summary>
        /// Indicates whether the configuration section has changed since
        /// it was last loaded
        /// </summary>
        /// <returns></returns>
        protected override bool IsModified()
        {
            return true;
        }

        #endregion

        #region private class XmlSectionWriter

        private class XmlSectionWriter 
            : XmlTextWriter
        {
            private bool skipAttribute = false;
            private readonly StringWriter stringWriter;

            public XmlSectionWriter(StringWriter w)
                : base(w)
            {
                stringWriter = w;
            }

            public override string ToString()
            {
                return stringWriter.ToString();
            }

            public override void WriteDocType(string name, string pubid, string sysid, string subset)
            {
                return;
            }

            public override void WriteStartDocument()
            {
                return;
            }

            public override void WriteStartDocument(bool standalone)
            {
                return;
            }

            public override void WriteStartAttribute(string prefix, string localName, string ns)
            {
                if (prefix == "xmlns" && (localName == "xsi" || localName == "xsd"))
                {
                    skipAttribute = true;
                    return;
                }
                base.WriteStartAttribute(prefix, localName, ns);
            }

            public override void WriteString(string text)
            {
                if (skipAttribute)
                {
                    return;
                }
                base.WriteString(text);
            }

            public override void WriteEndAttribute()
            {
                if (skipAttribute)
                {
                    skipAttribute = false;
                    return;
                }
                base.WriteEndAttribute();
            }
        }

        #endregion
    }
}
